﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using LoungeRepo.Contracts.EntityStore;

namespace LoungeRepo.DispatcherEntityStore
{
    public class RegexDispatcherEntityStore : IDispatcherEntityStore
    {
        private readonly Regex DEFAULT_PARTITION_NAME_PATTERN = new Regex(".*", RegexOptions.Compiled);

        internal class State
        {
            internal class PartitionEntityStoreRegistration
            {
                public Regex PartitionNamePattern;
                public IEntityStore EntityStore;
            }

            public List<PartitionEntityStoreRegistration> RegisteredEntityStores = new List<PartitionEntityStoreRegistration>();
        }

        private State state;


        public RegexDispatcherEntityStore() : this(new State()) {}
        internal RegexDispatcherEntityStore(State state)
        {
            this.state = state;
            RegisterExceptionEntityStoreAsInitialDefault();
        }


        public void RegisterDefaultPartitionEntityStore(IEntityStore partitionEntityStore)
        {
            // The default entity store is always the last one in the list
            // Since the ctor registeres a NullEntityStore as default we can expect here
            // to always find a default entity store.
            this.state.RegisteredEntityStores.RemoveAt(this.state.RegisteredEntityStores.Count - 1);
            this.state.RegisteredEntityStores.Add(new State.PartitionEntityStoreRegistration
                                                      {
                                                          PartitionNamePattern = DEFAULT_PARTITION_NAME_PATTERN,
                                                          EntityStore = partitionEntityStore
                                                      });
        }


        public void RegisterPartitionEntityStore(Regex paritionNamePattern, IEntityStore partitionEntityStore)
        {
            // Append entity stores in the order they are registered.
            // Since the default entity store is supposed to be always the last one, this means
            // all other entity stores need to be inserted right before it.
            this.state.RegisteredEntityStores.Insert(
                this.state.RegisteredEntityStores.Count - 1,
                new State.PartitionEntityStoreRegistration
                    {
                        PartitionNamePattern = paritionNamePattern,
                        EntityStore = partitionEntityStore
                    }
                );
        }


        #region IEntityStore interface members
        public void Store(IStreamedEntity streamedEntity)
        {
            FindEntityStoreForPartition(streamedEntity.Partition).Store(streamedEntity);
        }

        public IStreamedEntity Load(string id, string partition)
        {
            return FindEntityStoreForPartition(partition).Load(id, partition);
        }

        public void Delete(string id, string partition)
        {
            FindEntityStoreForPartition(partition).Delete(id, partition);
        }

        #endregion


        public void Dispose()
        {
            if (this.state != null)
            {
                foreach (IEntityStore entityStore in this.state.RegisteredEntityStores.Select(r => r.EntityStore))
                    entityStore.Dispose();
                this.state = null;
            }
        }

        
        private void RegisterExceptionEntityStoreAsInitialDefault()
        {
            if (this.state.RegisteredEntityStores.Count == 0)
                this.state.RegisteredEntityStores.Add(new State.PartitionEntityStoreRegistration
                {
                    PartitionNamePattern = DEFAULT_PARTITION_NAME_PATTERN,
                    EntityStore = new NullEntityStore()
                });
        }

        
        internal IEntityStore FindEntityStoreForPartition(string partition)
        {
            foreach(var registration in this.state.RegisteredEntityStores)
            {
                if (registration.PartitionNamePattern.Match(partition).Success)
                    return registration.EntityStore;
            }

            throw new InvalidOperationException(string.Format("No matching entity store found for partition '{0}'! Check registration of default entity store.", partition));
        }
    }
}
